import merge from "lodash/merge";
import { readFile, exists, writeFile } from "./file";

const errUtil = (error: Error, msg: string) => {
  // eslint-disable-next-line no-param-reassign
  error.message = `${msg} ${error.message}`;
  return error;
};

class Store {
  name: string;

  initDone: boolean;

  fileName: string;

  constructor(name: string) {
    this.name = name;
    this.initDone = false;
    this.fileName = `${wx.env.USER_DATA_PATH}/${this.name}.rnjs`;
  }

  async init() {
    if (this.initDone) {
      return;
    }
    try {
      const isExists = await exists(this.fileName);
      if (isExists) {
        return;
      }
      this.initDone = true;
      await writeFile(this.fileName, "{}");
    } catch (error) {
      throw errUtil(error, "error in init");
    }
  }

  async getItem(key: string): Promise<string | null> {
    await this.init();
    try {
      const fileRes = await readFile(this.fileName);
      if (fileRes.success) {
        const items = JSON.parse(fileRes.data as string);
        return items[key];
      } else {
        return null;
      }
    } catch (error) {
      throw errUtil(error, "error in getItem");
    }
  }

  async setItem(key: string, value: string) {
    await this.init();
    try {
      const fileRes = await readFile(this.fileName);
      if (fileRes.success) {
        const items = JSON.parse(fileRes.data as string);
        items[key] = value;
        return writeFile(this.fileName, JSON.stringify(items));
      }
      return null;
    } catch (error) {
      throw errUtil(error, "error in setItem");
    }
  }

  async removeItem(key: string) {
    await this.init();
    try {
      const fileRes = await readFile(this.fileName);
      if (fileRes.success) {
        const items = JSON.parse(fileRes.data as string);
        delete items[key];
        return writeFile(this.fileName, JSON.stringify(items));
      }
      return null;
    } catch (error) {
      throw errUtil(error, "error in removeItem");
    }
  }

  async mergeItem(key: string, value: string) {
    await this.init();
    try {
      const fileRes = await readFile(this.fileName);
      if (fileRes.success) {
        const items = JSON.parse(fileRes.data as string);
        items[key] = merge(items[key], value);
        return writeFile(this.fileName, JSON.stringify(items));
      }
      return null;
    } catch (error) {
      throw errUtil(error, "error in mergeItem");
    }
  }

  async clear() {
    await this.init();
    try {
      return writeFile(this.fileName, "{}");
    } catch (error) {
      throw errUtil(error, "error in clear");
    }
  }

  async multiGet(
    keys: string[],
    cb: (errors?: Error[] | null, result?: [string, string | null][]) => void
  ): Promise<[string, string | null][]> {
    await this.init();
    if (!Array.isArray(keys)) {
      throw new Error("input to multiGet is not an array.");
    }
    try {
      const fileRes = await readFile(this.fileName);
      if (fileRes.success) {
        const items = JSON.parse(fileRes.data as string);
        const output = keys.map((key): [string, string | null] => [
          key,
          items[key],
        ]);
        if (cb && typeof cb === "function") {
          cb(null, output);
        }
        return output;
      }
      return [];
    } catch (error) {
      if (cb && typeof cb === "function") {
        cb(error);
      }
      throw errUtil(error, "error in multiGet");
    }
  }

  async getAllKeys(
    cb?: (error?: Error | null, keys?: string[]) => void
  ): Promise<string[]> {
    await this.init();
    try {
      const fileRes = await readFile(this.fileName);
      if (fileRes.success) {
        const items = JSON.parse(fileRes.data as string);
        const output = Object.keys(items);
        if (cb && typeof cb === "function") {
          cb(null, output);
        }
        return output;
      }
      return [];
    } catch (error) {
      if (cb && typeof cb === "function") {
        cb(error);
      }
      throw errUtil(error, "error in multiGet");
    }
  }

  async multiSet(pairs: string[][]) {
    await this.init();
    if (!Array.isArray(pairs)) {
      throw new Error("input to multiSet is not an array.");
    }
    try {
      const fileRes = await readFile(this.fileName);
      if (fileRes.success) {
        const items = JSON.parse(fileRes.data as string);
        pairs.forEach((pair) => {
          if (Array.isArray(pair) && pair.length > 1) {
            // eslint-disable-next-line prefer-destructuring
            items[pair[0]] = pair[1];
          }
        });
        return writeFile(this.fileName, JSON.stringify(items));;
      }
      return null;
    } catch (error) {
      throw errUtil(error, "error in multiSet");
    }
  }

  async multiRemove(keys: string[]) {
    await this.init();
    if (!Array.isArray(keys)) {
      throw new Error("input to multiRemove is not an array.");
    }
    try {
      const fileRes = await readFile(this.fileName);
      if (fileRes.success) {
        const items = JSON.parse(fileRes.data as string);
        keys.forEach((key) => {
          delete items[key];
        });
        return writeFile(this.fileName, JSON.stringify(items));;
      }
      return null;
    } catch (error) {
      throw errUtil(error, "error in multiRemove");
    }
  }

  async multiMerge(pairs: string[][]) {
    await this.init();
    if (!Array.isArray(pairs)) {
      throw new Error("input to multiMerge is not an array.");
    }
    try {
      const fileRes = await readFile(this.fileName);
      if (fileRes.success) {
        const items = JSON.parse(fileRes.data as string);
        pairs.forEach((pair) => {
          if (Array.isArray(pair) && pair.length > 1) {
            items[pair[0]] = merge(items[pair[0]], pair[1]);
          }
        });
        return writeFile(this.fileName, JSON.stringify(items));;
      }
      return null;
    } catch (error) {
      throw errUtil(error, "error in multiMerge");
    }
  }
}

export default Store;
